/*
 * DefaultShader.cpp
 *
 *  Created on: 24-06-2013
 *      Author: Jarek
 */

#include "DefaultShader.h"
#include "EMesh.h"
#include "EModel.h"
#include "ETexture.h"

DefaultShader::DefaultShader(char* VSPath, char* FSPath, uint lightsCount)
{
	this->lightCount = lightsCount;
	uniforms.lightUniforms = new Uniforms::LightUniforms[lightsCount];
	createShader(VSPath, FSPath);
	createProgram();

	uniforms.modelMatrix 				= glGetUniformLocation(programHandler, "modelMatrix");
	uniforms.viewMatrix 				= glGetUniformLocation(programHandler, "viewMatrix");
	uniforms.projectionMatrix 			= glGetUniformLocation(programHandler, "projMatrix");
	uniforms.modelViewProjectionMatrix 	= glGetUniformLocation(programHandler, "modelViewProjMatrix");
	uniforms.viewInverseMatrix			= glGetUniformLocation(programHandler, "viewInverseMatrix");
	uniforms.diffuseTexture				= glGetUniformLocation(programHandler, "diffuseTexture");
	uniforms.textureScale				= glGetUniformLocation(programHandler, "diffuseTextureScale");

	char buffer[128];
	for (uint i = 0; i < lightsCount; i++)
	{
		sprintf(buffer, "shadowMaps[%d]", i);
		uniforms.lightUniforms[i].shadowMapHandle		= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "biasLightViewProjMatrix[%d]", i);
		uniforms.lightUniforms[i].lightMVPMatrix 		= glGetUniformLocation(programHandler, buffer);

		sprintf(buffer, "light%d.position", i);
		uniforms.lightUniforms[i].position				= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.spotCutoff", i);
		uniforms.lightUniforms[i].spotCutoff			= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.spotExponent", i);
		uniforms.lightUniforms[i].spotExponent			= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.spotDirection", i);
		uniforms.lightUniforms[i].spotDirection			= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.constantAttenuation", i);
		uniforms.lightUniforms[i].constantAttenuation	= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.linearAttenuation", i);
		uniforms.lightUniforms[i].linearAttenuation		= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.quadraticAttenuation", i);
		uniforms.lightUniforms[i].quadraticAttenuation	= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.diffuse", i);
		uniforms.lightUniforms[i].diffuse				= glGetUniformLocation(programHandler, buffer);
		sprintf(buffer, "light%d.specular", i);
		uniforms.lightUniforms[i].specular				= glGetUniformLocation(programHandler, buffer);
	}

	attributes.position							= glGetAttribLocation(programHandler, "position");
	attributes.normal							= glGetAttribLocation(programHandler, "normal");
	attributes.texCoords						= glGetAttribLocation(programHandler, "texCoords");

	if (uniforms.modelMatrix < 0)
		LOGE("DefaultShader: couldn't find uniform \"modelMatrix\".");
	if (uniforms.viewMatrix < 0)
		LOGE("DefaultShader: couldn't find uniform \"viewMatrix\".");
	if (uniforms.projectionMatrix < 0)
		LOGE("DefaultShader: couldn't find uniform \"projMatrix\".");
	if (uniforms.modelViewProjectionMatrix < 0)
		LOGE("DefaultShader: couldn't find uniform \"modelViewProjMatrix\".");
	if (uniforms.modelInverseTransposeMatrix < 0)
		LOGE("DefaultShader: couldn't find uniform \"modelInverseTransposeMatrix\".");
	if (uniforms.viewInverseMatrix < 0)
		LOGE("DefaultShader: couldn't find uniform \"viewInverseMatrix\".");
	if (uniforms.diffuseTexture	< 0)
		LOGE("DefaultShader: couldn't find uniform \"diffuseTexture\".");

	for (uint i = 0; i < lightsCount; i++)
	{
		if (uniforms.lightUniforms[i].shadowMapHandle 		< 0)
			LOGE("DefaultShader: couldn't find uniform \"shadowMapHandle[%d]\".", i);
		if (uniforms.lightUniforms[i].lightMVPMatrix 		< 0)
			LOGE("DefaultShader: couldn't find uniform \"lightParameters[%d]\".", i);
		if (uniforms.lightUniforms[i].position 			< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].position\".", i);
		if (uniforms.lightUniforms[i].spotCutoff 			< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].spotCutoff\".", i);
		if (uniforms.lightUniforms[i].spotExponent 		< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].spotExponent\".", i);
		if (uniforms.lightUniforms[i].spotDirection 		< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].spotDirection\".", i);
		if (uniforms.lightUniforms[i].constantAttenuation 	< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].constantAttenuation\".", i);
		if (uniforms.lightUniforms[i].linearAttenuation 	< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].linearAttenuation\".", i);
		if (uniforms.lightUniforms[i].quadraticAttenuation < 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].quadraticAttenuation\".", i);
		if (uniforms.lightUniforms[i].diffuse 				< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].diffuse\".", i);
		if (uniforms.lightUniforms[i].specular 			< 0)
			LOGE("DefaultShader: couldn't find uniform \"lights[%d].specular\".", i);
	}

	if (attributes.position < 0)
		LOGE("DefaultShader: couldn't find attribute \"position\".");
	if (attributes.normal < 0)
		LOGE("DefaultShader: couldn't find attribute \"normal\".");
	if (attributes.texCoords < 0)
		LOGE("DefaultShader: couldn't find attribute \"texCoords\".");

}

DefaultShader::~DefaultShader()
{
	DELETE_ARRAY(uniforms.lightUniforms);
}

void DefaultShader::useAttributes(bool isAttributes) {
	if(isAttributes)
	{
		glEnableVertexAttribArray(attributes.position);
		glEnableVertexAttribArray(attributes.normal);
		glEnableVertexAttribArray(attributes.texCoords);
	}
	else
	{
		glDisableVertexAttribArray(attributes.position);
		glDisableVertexAttribArray(attributes.normal);
		glDisableVertexAttribArray(attributes.texCoords);
	}
}

void DefaultShader::useShaderOnMesh(const Mesh& mesh)
{
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.facesBuffer);

	if(attributes.position > 0 && mesh.verticesBuffer > 0)
	{
		glBindBuffer(GL_ARRAY_BUFFER, mesh.verticesBuffer);
		glEnableVertexAttribArray(attributes.position);
		glVertexAttribPointer(attributes.position, 3, GL_FLOAT, GL_FALSE, 0, 0);
	}

	if (attributes.normal >= 0 && mesh.normalsBuffer > 0)
	{
		glBindBuffer(GL_ARRAY_BUFFER, mesh.normalsBuffer);
		glEnableVertexAttribArray(attributes.normal);
		glVertexAttribPointer(attributes.normal, 3, GL_FLOAT, GL_FALSE, 0, 0);
	}

	if (attributes.texCoords >= 0 && mesh.texcoordsBuffer > 0)
	{
		glBindBuffer(GL_ARRAY_BUFFER, mesh.texcoordsBuffer);
		glEnableVertexAttribArray(attributes.texCoords);
		glVertexAttribPointer(attributes.texCoords, 2, GL_FLOAT, GL_FALSE, 0, 0);
	}

	if (uniforms.diffuseTexture >= 0 && mesh.diffuseTexture != NULL && mesh.diffuseTexture->handle > 0)
	{
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture->handle);
		glUniform1i(uniforms.diffuseTexture, 0);
	}
	else
	{
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, 0);
		glUniform1i(uniforms.diffuseTexture, 0);
	}
}

void DefaultShader::draw(const ETime& time, const World& world, View& camera, const Texture* shadowMaps, const uint shadowMapsCount)
{
	this->useProgram();
	for(uint i = 0; i < shadowMapsCount; i++)
	{
		glActiveTexture(GL_TEXTURE1 + i);
		glBindTexture(GL_TEXTURE_2D, shadowMaps[i].handle);
		glUniform1i(uniforms.lightUniforms[i].shadowMapHandle, 1 + i);

		Matrix4x4<float> 		lm	= world.lights[i].getWorldMatrix(time);
		Vector3<float> 			position = lm.translation();
		Vector3<float>			direction = -lm.forward();

		glUniform3fv(uniforms.lightUniforms[i].position, 1, &position.x);
		glUniform1f(uniforms.lightUniforms[i].spotCutoff, world.lights[i].spotCutoff);
		glUniform1f(uniforms.lightUniforms[i].spotExponent, world.lights[i].spotExponent);
		glUniform3fv(uniforms.lightUniforms[i].spotDirection, 1, &direction.x);
		glUniform1f(uniforms.lightUniforms[i].constantAttenuation, world.lights[i].constantAttenuation);
		glUniform1f(uniforms.lightUniforms[i].linearAttenuation, world.lights[i].linearAttenuation);
		glUniform1f(uniforms.lightUniforms[i].quadraticAttenuation, world.lights[i].quadraticAttenuation);
		glUniform4fv(uniforms.lightUniforms[i].diffuse, 1, &world.lights[i].diffuse.x);
		glUniform4fv(uniforms.lightUniforms[i].specular, 1, &world.lights[i].specular.x);
	}

	Matrix4x4<float> viewMatrix = camera.getModelViewMatrix(time, Matrix4x4<float>::getIdentity());
	setUniformMatrix4x4f(uniforms.viewMatrix, &viewMatrix.at(0, 0));
	setUniformMatrix4x4f(uniforms.viewInverseMatrix, &viewMatrix.getInverted().at(0, 0));

	drawM(time, world, *world.root, camera);
}

void DefaultShader::drawM(const ETime& time, const World& world, Node& node, View& camera)
{
	if(node.nodeType == Node::NODE_TYPE_MODEL)
	{
		Model& model = *static_cast<Model*>(&node);

		Matrix4x4<float> worldMatrix = model.getWorldMatrix(time);
		for(uint i = 0; i < this->lightCount; i++)
		{
			setUniformMatrix4x4f(uniforms.lightUniforms[i].lightMVPMatrix, &world.lights[i].getModelViewProjectionMatrix(time, worldMatrix).at(0,0));
		}

		setUniformMatrix4x4f(uniforms.modelMatrix, &worldMatrix.at(0, 0));
		setUniformMatrix4x4f(uniforms.modelViewProjectionMatrix, &camera.getModelViewProjectionMatrix(time, worldMatrix).at(0, 0));
		setUniformMatrix3x3f(uniforms.modelInverseTransposeMatrix, &worldMatrix.get3x3().getInversedTransposed().at(0, 0));

		for(uint j = 0; j < model.meshesCount; j++)
		{
			useShaderOnMesh(*model.meshes[j]);
			glDrawElements(GL_TRIANGLES, model.meshes[j]->facesCount * 3, GL_UNSIGNED_INT, 0);
		}
	}

	for(uint k = 0; k < node.childrenCount; k++)
	{
		drawM(time, world, *node.children[k], camera);
	}
}

